Skip to content

skills(objectstack-data): the tree rows state the landed reference contract - #16187

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-16083-tree-reference-rows
Sep 7, 2026
Merged

skills(objectstack-data): the tree rows state the landed reference contract#16187
os-zhuang merged 1 commit into
mainfrom
claude/issue-16083-tree-reference-rows

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #16083

refuseForeignTreeReference (packages/spec/src/data/object.zod.ts, on main since fb77aa541) makes a tree field's reference optional, and — when written — equal to the declaring object's own name (the extend target on an extension). Any other object is refused at parse with one custom issue at fields.FIELD.reference. The published objectstack-data skill still listed reference as a bare key prop, so a client agent loading the skill had no way to know either half of the rule. content/docs already says it; these two rows were the last surface that did not.

Two rows, no new section

File Before After
rules/field-types.md (Relational table) key config was just `reference` `reference` — optional; when written, this object's own name (any other object is refused at parse)
rules/relationships.md (Relationship Types table) the Required cell was a bare Optional says what is optional, and contrasts "refused at parse" with the master_detail row's "lint-warned" in that same column

Wording is taken from the refusal message and from the already-landed content/docs sentences (validation-rules.mdx:297, field-type-decision-tree.mdx:183), not re-invented — the card's acceptance asks for the words the spec's refusal uses.

Unchanged on purpose: the Hierarchical: tree (Self-Reference) example. It already writes the object's own name (reference: 'category' on object category), which is all the ruling asks of it. I drafted a clarifying comment on that line and then dropped it: the row three screens up in the same file now states the rule, so the comment would have cost 28 bytes without changing what a client agent writes.

Ratchet accounting — and the deletion that paid for it

The token ratchet left field-types.md 27 bytes under its ceiling, and the shortest faithful clause for that row is ~88 bytes. The deletion is therefore compelled by the ratchet, not elective tidying.

What was spent: the Media section's two-line note "There is no per-field attachment config (size limits, allowed types, storage) — storage concerns live outside the field schema." It restates the top-of-file blockquote, which sits above every table and already refuses the exact key by name:

Per-type display knobs beyond these do not exist — an unknown field key is REFUSED at parse (unrecognized_keys), so don't invent theme, rows, or fileAttachmentConfig.

grep confirms the note appears nowhere else in skills/. Removing it changes nothing a client agent writes; the operative refusal and the exact invented key both survive.

File tokens before tokens after ceiling headroom now
rules/field-types.md 3578 3567 3584 17
rules/relationships.md 3490 3522 3778 256

field-types.md shrinks. The package moves +21 tokens net (140376 → 140397 of a 157650 ceiling) — stated plainly rather than rounded away, and paid for inside the two files the ruling names.

Gates — union re-run on the final commit 5a30844272

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 20 families from the real changeset; all 20 run, reconciled with --ran:

✓ dispatch-gates --ran: 20 derived famil(ies) accounted for — 20 run, 0 NOT-MEASURED.

Every exit code was captured by redirecting first, never through a pipe.

  • ✓ check-skills-token-ratchet: 36 authored bundle file(s) within their ceilings — plus ✓ … self-test: 64 cases pass.
  • check-nul-bytes: OK (scanned 7932 text file(s) … no raw ASCII control bytes)
  • whole-repo pnpm lint (eslint . --no-inline-config) — exit 0, not narrowed. It ran on this commit's exact tree (git diff HEAD and git status --porcelain both empty since).
  • check:pm-governed-merges, check:skill-frame-sync, check:skill-compatibility, check:skill-identifier-liveness, check:doc-authoring, check-published-list-mirrors (+ self-test) — all exit 0.
  • check-governed-merges --test on both paths — exit 3, GOVERNED, as expected: skills/** ×2 — the published skills catalog.
  • One gate first came back PREREQUISITE NOT MET (exit 3, nothing measured) because @objectstack/formula was unbuilt; after turbo run build, check:doc-formula-expressions is exit 0.

Two gates named in the brief, check-doc-snippet-types and check-skill-examples, do not exist in this repo — no script, no package.json entry. check-skill-id-lint exists but its corpus is .claude/skills/pm-dispatch plus .claude/agents/os-dev.md and AGENTS.md; the published skills/** roots are not in it, so it does not read these files. Run anyway: exit 0.

No changeset, and skip-changeset is applied: skills/ is not a workspace package and no package.json files array names it, so this diff releases nothing. Six landed skills/**-only commits carry zero changesets.

维护者速读(草稿)

发布给客户 agent 的 objectstack-data 技能里,tree 字段的 reference 一直只是裸列的一个键。契约那半边早已落地——写别的对象在 parse 就被拒——但技能文本没说。客户拿我们的 AI 写元数据 app 时,读到的是「tree 要配 reference」,于是写出指向另一个对象的 tree,一路到 publish 才被拒。这是沉默造成的返工,不是错误陈述。

改动是两行表格,不加新章节:field-types.md 的 Relational 表 tree 行,和 relationships.md 关系类型表的 tree 行。措辞直接取自 spec 的拒收信封,以及 content/docs 上个 PR 已经落地的同一句话,没有另造一套说法——分叉的措辞就是下一张卡。

要请您看一眼的是那处删除。token 棘轮把 field-types.md 卡在离天花板 27 字节,而把契约在那一行说清楚最少要 88 字节:删是被棘轮逼出来的,不是顺手清理。删掉的是 Media 段那句「没有 per-field attachment config」——它复述的正是文件开头那段引用块,而那段已经点名拒收 fileAttachmentConfig,位置还在所有表格之上。删完该文件净缩。

没动的地方也请留意:tree 的示例一个字没改,它本来就写着对象自己的名字。示例行上我起过一句注释,又拿掉了——同一文件的表格行已经把规则说了,再写一遍只是多花字节,不改变客户 agent 写出来的东西。

治理面:skills/** 在名册上,所以这是 draft PR、人工合并,席位不 flip ready、不入队、不 approve。纯散文改动,无契约移动,无 changeset。整包净增 21 tokens,已如实写在上面的表里。

席位意见:


Generated by Claude Code

…` contract

`refuseForeignTreeReference` (packages/spec/src/data/object.zod.ts) makes a
`tree` field's `reference` optional and, when written, equal to the declaring
object's own name — the `extend` target on an extension. Any other object is
refused at parse with one issue at `fields.<field>.reference`. The published
skill still listed `reference` as a bare key prop, so a client agent reading it
had no way to know either half of the rule.

Two rows, no new section:

- `rules/field-types.md` — the Relational table's `tree` row now states
  optional-and-self-only and that a foreign object is refused at parse.
- `rules/relationships.md` — the `tree` row's bare "Optional" cell now says
  what is optional and contrasts the refusal with the `master_detail` row's
  lint-warned cell in the same column.

The token ratchet left `field-types.md` 27 bytes under its ceiling, so the row
is paid for inside the same file: the Media section's "no per-field attachment
config" note restated the top-of-file blockquote, which already refuses
`fileAttachmentConfig` by name, above every table. That file now shrinks.

The `Hierarchical: tree (Self-Reference)` example is unchanged — it already
writes the object's own name (`reference: 'category'` on `category`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 6, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 6, 2026
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — governed in-seat review of PR #16187, head 5a30844272 (skills seat, 2026-09-06T14:2xZ; the compare-worktree reading was first taken at 05:4xZ and re-taken on a fresh worktree at 14:1xZ after the seat's container was reclaimed).

Implemented-by: claude/issue-16083-tree-reference-rows (os-dev subagent, opus under the measured quota exemption recorded on the claim)
Reviewed-by: session_019RfFHiRCSs3JXLK4cwcfox (skills seat)

Verified in a detached compare worktree at the PR head, not from the report:

  • Two files, +2 / −5, both under skills/objectstack-data/rules/. The tree row of field-types.md's Relational table now reads `reference` — optional; when written, this object's own name (any other object is refused at parse); the tree row of relationships.md's Relationship Types table now says what its bare Optional meant and contrasts "refused at parse" with the master_detail row's "lint-warned" in the same column. Both sentences are the spec's own refusal (refuseForeignTreeReference) and the already-landed content/docs wording, not a third phrasing. The Hierarchical: tree (Self-Reference) example is untouched and already writes the object's own name.
  • The paid deletion is compelled, not elective. field-types.md sat 27 bytes under its ceiling; the Media section's two-line "no per-field attachment config" note restates the top-of-file blockquote that already refuses fileAttachmentConfig by name above every table. The file shrinks (3578 → 3567 of 3584); relationships.md 3490 → 3522 of 3778; package +21 tokens, stated in the PR body rather than rounded away. The seat's own grep for # followed by three or more digits over the changed file returns one hit, a hex colour in a code sample, not an issue number.
  • Gates at the head: check-skills-token-ratchet exit 0 ("36 authored bundle file(s) within their ceilings"); check-governed-merges --test on the two paths exit 3 (GOVERNED: skills/**). The dev's report adds the derived union 20/20 families run, --ran reconciled at 0 NOT-MEASURED, whole-repo pnpm lint exit 0 unnarrowed. PR checks on 5a30844272: 23 success, 11 skipped, 0 failed.
  • Report hygiene: dev report on skills(objectstack-data): the tree rows in rules/field-types.md and rules/relationships.md state the landed reference contract — optional, and the object's own name when present (#14892 follow-up) #16083 with the literal os-dev-report line, JSON closes; PR body opens Fixes #16083, carries the 改前 → 改后 table, the ratchet accounting and a ## 维护者速读(草稿) block; no model identifier; skip-changeset read back (objectstack's decider says no changeset is owed for skills/** prose). One MCP call declared (a search_issues that was refused by the rate limit; the dedup was then handed to the seat).

Ruling on the open question — A. +21 tokens at package level is accepted: the per-file ratchet is the gate, both files are under ceiling, and the ruling behind the ratchet targets padding a small feature into prose, which two contract sentences are not. B (an unrelated 84-byte cut to reach net zero) would be a rider on a governed surface. C is queued: the seat files the dev's reported finding (the ~1800-byte ASCII decision tree in field-types.md duplicating the mermaid page in content/docs) as the durable headroom fix, after a dedup over every open domain:skills card.

Landing regime: governed (skills/**) ⇒ the PR stays a draft; reviewers os-zhuang + hotlong requested in this same act, needs-user-decision added, the final 维护者速读 posted as the next comment; human merge. Nothing is flipped, queued or approved from the seat.


Generated by Claude Code

@claude
claude Bot requested review from hotlong and os-zhuang September 6, 2026 14:19
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读

事情:发布给客户 agent 的 objectstack-data 技能里,tree 字段的 reference 一直只是裸列的一个键。契约那半边早已落地——reference 可省略,写了就必须是对象自己的名字,写别的对象在 parse 就被拒——但技能文本没说。客户拿我们的 AI 写元数据 app 时,读到的是「tree 要配 reference」,于是写出指向别的对象的 tree,一路到 publish 才被拒。这是沉默造成的返工,不是错误陈述。

改动:两行表格,不加新章节——field-types.md Relational 表的 tree 行,和 relationships.md 关系类型表的 tree 行。措辞直接取自 spec 的拒收信封和 content/docs 已落地的同一句话,没有另造一套说法。

请您看一眼的是那处删除:token 棘轮把 field-types.md 卡在离天花板 27 字节,而把契约说清楚最少要 88 字节,删是被棘轮逼出来的。删掉的是 Media 段那句「没有 per-field attachment config」——它复述的正是文件开头那段引用块,那段已经点名拒收 fileAttachmentConfig,位置在所有表格之上。删完该文件净缩(3578 → 3567),整包净增 21 tokens。

没动的地方:tree 的示例一个字没改,它本来就写着对象自己的名字。

治理面:skills/** 在名册上,所以是 draft PR、人工合并;席位不 flip ready、不入队、不 approve。纯散文改动,无契约移动,无 changeset。

席位意见:建议合并。整包 +21 tokens 席位已裁为可接受(按文件棘轮是门,两个文件都在顶下)。顺带一条已立卡、不在本 PR 里的事:field-types.md 里还有一段约 1800 字节的 ASCII「字段类型决策树」,和 content/docs 的 mermaid 页是同一棵树,砍掉能腾出约 450 tokens 的余量——下一位要在这个文件加一行的人,不砍就会撞上同一堵墙。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 7, 2026 00:48
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit fef3216 Sep 7, 2026
40 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-16083-tree-reference-rows branch September 7, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-user-decision size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants